<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8" />
  <title>WebRTC 请求视频</title>
</head>

<body>
  <h2>WebRTC 播放视频</h2>
  <video id="remoteVideo" autoplay playsinline controls style="width: 960px; height: 540px;"></video>
  <br><br>
  <button onclick="startPlay()">开始播放</button>
  <button onclick="stopPlay()">停止播放</button>
  <button onclick="startListen()">监听</button>
  <button onclick="startTalk()">对讲</button>
  <button onclick="sendMsg()">发消息</button>
  <script>

    var dataChannel;
    var pc;
    var videoElement;
    async function startPlay() {

      //1.对did进行签名
      const ips = "120.78.4.34,125.122.23.176,101.133.234.243";//p2p服务器地址
      const did = 'HIXX00000036KPMJH';
      const sign = '9771D2035D1A5A70';

      //2.发起POST请求，获取播放地址
      const res = await fetch(`http://120.78.4.34/hl_web?did=${did}&sign=${sign}&ips=${ips}&type=0`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/wasm'
        },
        body: 'kaven'  // 发送 Offer
      });
      const d = await res.json();
      console.log(`请求返回 ${JSON.stringify(d)}`);
      if (d.code < 0) {
        alert(d.msg);
        return;
      }

      // 3.创建媒体流容器
      const mediaStream = new MediaStream();
      videoElement = document.getElementById('remoteVideo'); //这是播放器
      videoElement.srcObject = mediaStream;

      //4.创建webrtc
      pc = new RTCPeerConnection();
      pc.addTransceiver('video', { direction: 'recvonly' }); // 添加视频接收器（关键步骤！）
      pc.addTransceiver('audio', { direction: 'recvonly' }); // 添加音频接收器（关键步骤！）

      // 创建数据通道
      dataChannel = pc.createDataChannel('hlcnl', {
        ordered: true
      });

      // 监听数据通道状态
      dataChannel.onopen = () => {
        console.log('Data channel opened');
        // 通道打开后先向设备发一个固定的值
        dataChannel.send('hlack');
      };

      dataChannel.onclose = () => {
        console.log('Data channel closed');
      };

      dataChannel.onerror = (error) => {
        console.log('Data channel error:', error);
      };

      // 监听接收到的数据
      dataChannel.onmessage = (event) => {
        const data = event.data;

        // 判断数据类型并相应处理
        if (typeof data === 'string') {
          console.log('收到文本消息:', data);

        } else if (data instanceof ArrayBuffer) {
          console.log('收到二进制数据, 大小:', data.byteLength, 'bytes');

        } else if (data instanceof Blob) {
          console.log('收到Blob数据, 类型:', data.type, '大小:', data.size);
        }
      };

      pc.ontrack = function (event) { // 收到 track 后绑定到 video 元素
        console.log(`ontrack() kind=${event.track.kind}`);
        if (event.track.kind === 'video' || event.track.kind === 'audio') {
          mediaStream.addTrack(event.track);
        }
      };

      // 创建 Offer
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);

      // 发送 Offer 到 服务器 获取 Answer
      const response = await fetch(d.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/sdp'
        },
        body: pc.localDescription.sdp  // 发送 Offer
      });

      if (!response.ok) {
        alert("HTTP 请求失败");
        return;
      }
      const data = await response.json();
      // console.log(`sdp请求返回 ${JSON.stringify(data)}`);
      if (data.code !== 0) {
        alert("获取 Answer 失败：" + data.msg);
        return;
      }

      // 设置 Answer
      const answerSdp = data.sdp;
      await pc.setRemoteDescription(new RTCSessionDescription({
        type: 'answer',
        sdp: answerSdp
      }));

      console.log("WebRTC 连接建立成功");
    }

    function stopPlay() {
      if (pc) {
        pc.close();
        pc = null;
      }
      videoElement.srcObject = null;
    }

    async function sendMsg() {
      dataChannel.send('web 发过来的消息');

      //2.发起POST请求 
      // const ips = "120.78.4.34,125.122.23.176,101.133.234.243";//p2p服务器地址
      // const did = 'HIXX00000002FBQRM';
      // const sign = '8920865DA34FE2B9';
      // const res = await fetch(`http://120.78.4.34/hl_web?did=${did}&sign=${sign}&ips=${ips}&type=3`, {
      //   method: 'POST',
      //   headers: {
      //     'Content-Type': 'application/wasm'
      //   },
      //   body: 'kaven'  // 发送 Offer
      // });
      // const d = await res.json();
      // console.log(`请求返回 ${JSON.stringify(d)}`);
    }

    function startListen() {
      const audioTracks = videoElement.srcObject.getAudioTracks();
      if (audioTracks.length > 0) {
        audioTracks.forEach(track => {
          console.log("音频轨道 enable=", track.enabled);
          track.enabled = !track.enabled;
        });
      }
    }

    function startTalk() {
      // dataChannel.send('hl web send msg');

      var audioBuffer = new Int16Array(0); // 音频缓存
      //获取音频权限
      let localStream = navigator.mediaDevices.getUserMedia({
        audio: true, // 只请求音频权限
        video: false // 不请求视频权限
      }).then(function (stream) {
        // 如果成功获取麦克风权限，这里的 stream 就是麦克风的音频流
        console.log("麦克风权限获取成功，音频流：", stream);
        let audioContext = new (window.AudioContext || window.webkitAudioContext)({
          sampleRate: 8000, // Hz 采样率
        });
        const source = audioContext.createMediaStreamSource(stream);
        const processor = audioContext.createScriptProcessor(1024, 1, 1);
        processor.onaudioprocess = (event) => {
          // 获取 PCM 数据 (16-bit, 8kHz)
          const inputData = event.inputBuffer.getChannelData(0);

          // 将浮点 PCM (-1.0 到 +1.0) 转换为 16-bit 整数 PCM
          const pcm16 = new Int16Array(inputData.length);
          for (let i = 0; i < inputData.length; i++) {
            pcm16[i] = Math.max(-32768, Math.min(32767, inputData[i] * 32768));
          }

          // 合并新数据到缓冲区
          const newBuffer = new Int16Array(audioBuffer.length + pcm16.length);
          newBuffer.set(audioBuffer, 0);
          newBuffer.set(pcm16, audioBuffer.length);
          audioBuffer = newBuffer;

          while (audioBuffer.length >= 320) {
            // 1280字节 = 640个16-bit样本 = 80ms音频数据（8kHz采样率）
            const sendData = audioBuffer.slice(0, 320);

            // 将 16-bit PCM 转换为 G.711 A-law
            const alawData = pcm16ToG711A(sendData);

            // 发送数据
            dataChannel.send(alawData);
            // console.log("音频：", alawData.length);

            // 保留剩余数据在缓冲区
            audioBuffer = audioBuffer.slice(320)
          }
        }
        // 连接节点
        source.connect(processor)
        processor.connect(audioContext.destination)
      }).catch(function (err) {
        // 如果获取麦克风权限失败，会进入这个 catch 块
        console.log("获取麦克风权限失败：", err);
      });
    }

    function pcm16ToG711A(pcm16) {
      const alawA = new Uint8Array(pcm16.length)

      for (let i = 0; i < pcm16.length; i++) {
        alawA[i] = lineToG711A(pcm16[i])
      }

      return alawA
    }

    function lineToG711A(sample) {
      const ALAW_MAX = 0x7fff
      const ALAW_BIAS = 0x84

      let sign, exponent, mantissa, alaw
      sign = (~sample >> 8) & 0x80

      if (!sign) {
        sample = -sample
        if (sample > ALAW_MAX) {
          sample = ALAW_MAX
        }
      }

      if (sample >= 256) {
        exponent = 7
        for (let mask = 0x4000; (sample & mask) === 0 && exponent > 0; mask >>= 1) {
          exponent--
        }

        mantissa = (sample >> (exponent + 3)) & 0x0f
        alaw = (exponent << 4) | mantissa
      } else {
        alaw = sample >> 4
      }
      alaw ^= sign ^ 0x55

      return alaw
    }

  </script>
</body>

</html>